package command

import (
	"fmt"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/csingo/cHelper"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"

	"gitee.com/csingo/csingo/resource/console"
	"gitee.com/csingo/csingo/resource/template"
)

type ConsoleCommand struct{}

func (i *ConsoleCommand) Desc() *CommandDesc {
	return &CommandDesc{
		Name: "console",
		Methods: []*CommandMethod{
			{
				Name: "template",
				Desc: "生成工具模板，工具开发者专用",
				Params: []*CommandParams{
					{Name: "tplPath", Desc: "模板目录绝对路径", Optional: false},
				},
			},
			{
				Name: "create",
				Desc: "创建命令行工具项目",
				Params: []*CommandParams{
					{Name: "name", Desc: "项目名称", Optional: false},
					{Name: "projectPath", Desc: "项目父级目录绝对路径，默认当前目录", Optional: true},
				},
			},
			{
				Name: "command",
				Desc: "创建指令",
				Params: []*CommandParams{
					{Name: "name", Desc: "指令名称", Optional: false},
				},
			},
			{
				Name: "model",
				Desc: "创建模型",
				Params: []*CommandParams{
					{Name: "name", Desc: "模型名称"},
					{Name: "table", Desc: "数据表名"},
					{Name: "dsn", Desc: "数据库连接, eg: mysql://username:password@ip:port/db"},
					{Name: "driver", Desc: "模型使用的数据库配置名，默认 default", Optional: true},
				},
			},
		},
	}
}

func (i *ConsoleCommand) Template(tplPath string) {
	ParamCheck("tplPath", tplPath)

	var err error

	currentDir := GetCurrentDir()
	templateDir := TrimPath(filepath.Clean(tplPath))

	// 删除当前项目模板文件
	err = os.RemoveAll(filepath.Clean(currentDir + "/template-console"))
	HandleError(err)

	err = filepath.WalkDir(templateDir, func(fPath string, d fs.DirEntry, err error) error {
		filename := filepath.Base(fPath)
		fPath = filepath.Clean(fPath)
		relativePath := strings.TrimPrefix(fPath, templateDir)
		absolutePath := filepath.Clean(fmt.Sprintf("%s/template-console%s", currentDir, relativePath))
		absoluteDir := filepath.Dir(absolutePath)

		// 过滤不处理的文件和文件夹
		if strings.Contains(absolutePath, ".git") && filename != ".gitignore" && filename != ".gitkeep" {
			return nil
		}
		if strings.Contains(absolutePath, ".idea") {
			return nil
		}

		if d.IsDir() {
			// 文件夹，直接创建
			err = os.MkdirAll(absolutePath, 0755)
			if err != nil {
				return err
			}
		} else {
			// 文件，先创建文件夹，再复制文件
			absolutePath = filepath.Clean(fmt.Sprintf("%s%s", absolutePath, ".tpl"))
			err = os.MkdirAll(absoluteDir, 0755)
			if err != nil {
				return err
			}
			content, err := ioutil.ReadFile(fPath)
			if err != nil {
				return err
			}
			err = ioutil.WriteFile(absolutePath, content, 0755)
			if err != nil {
				return err
			}
		}

		return err
	})
	HandleError(err)
}

func (i *ConsoleCommand) Create(name, projectPath string) {
	ParamCheck("name", name)

	var err error
	var currentDir string

	if projectPath == "" {
		currentDir = GetCurrentDir()
	} else {
		currentDir = TrimPath(filepath.Clean(projectPath))
	}
	projectDir := filepath.Clean(currentDir + "/" + name)
	// 创建项目目录
	log.Printf("创建项目: %s", projectDir)
	err = os.MkdirAll(projectDir, 0755)
	HandleError(err)
	// 复制项目文件
	files := console.AssetNames()
	for _, file := range files {
		targetFile := strings.TrimSuffix(file, ".tpl")
		targetFile = strings.TrimPrefix(targetFile, "template-console")
		targetFilePath := filepath.Clean(fmt.Sprintf("%s%s", projectDir, targetFile))

		// 创建文件所在目录
		targetFileDir := filepath.Dir(targetFilePath)
		err = os.MkdirAll(targetFileDir, 0755)
		HandleError(err)

		// 获取文件内容
		content, err := console.Asset(file)
		HandleError(err)

		// 替换文件内容
		source := string(content)
		source = strings.Replace(source, "gitee.com/csingo/example-cmd", name, -1)
		content = []byte(source)

		// 写入文件内容
		err = ioutil.WriteFile(targetFilePath, content, 0755)
		HandleError(err)
	}

	// 执行指令
	log.Println("安装并更新依赖包")
	Exec("go mod tidy", projectDir)
	Exec("go get -u all", projectDir)
}

func (i *ConsoleCommand) Command(name string) {
	ParamCheck("name", name)

	// 判断command目录是否存在
	currentDir := GetCurrentDir()
	targetFileDir := filepath.Clean(fmt.Sprintf("%s/command", currentDir))
	if exits, err := PathExists(targetFileDir); !exits || err != nil {
		HandleError(targetFileDir + "目录不存在")
	}
	targetFilePath := filepath.Clean(fmt.Sprintf("%s/%s.go", targetFileDir, name))

	if !cHelper.IsExistsPath(targetFilePath) {
		content, err := console.Asset("template-console/command/HelloCommand.go.tpl")
		if err != nil {
			HandleError(err)
		}

		source := strings.Replace(string(content), "HelloCommand", name, -1)
		source = strings.Replace(string(source), "hello", cHelper.ToSnake(strings.TrimSuffix(name, "command")), -1)
		content = []byte(source)

		err = os.WriteFile(targetFilePath, content, 0755)
		if err != nil {
			HandleError(err)
		}
	}

	// 判断autoload文件是否存在
	autoloadFilePath := filepath.Clean(fmt.Sprintf("%s/autoload/Command.go", currentDir))
	if exits, err := PathExists(targetFileDir); !exits || err != nil {
		HandleError(autoloadFilePath + "文件不存在")
	}

	project, err := ProjectName()
	if err != nil {
		HandleError(err)
	}
	autoloadContentBytes, err := os.ReadFile(autoloadFilePath)
	if err != nil {
		HandleError(err)
	}
	autoloadContent := string(autoloadContentBytes)
	if !strings.Contains(autoloadContent, "import (") {
		autoloadContent = strings.ReplaceAll(autoloadContent, "package autoload", fmt.Sprintf("package autoload\n\nimport (\n\t\"gitee.com/csingo/cServer\"\n\n\t\"%s/command\"\n)", project))
	}

	injectContent := fmt.Sprintf("cServer.Inject(&command.%s{})", name)
	if !strings.Contains(autoloadContent, injectContent) {
		autoloadContent = strings.ReplaceAll(autoloadContent, "func LoadCommand() {", fmt.Sprintf("func LoadCommand() {\n\t%s", injectContent))
	}
	err = os.WriteFile(autoloadFilePath, []byte(autoloadContent), 0755)
	if err != nil {
		HandleError(err)
	}
}

func (i *ConsoleCommand) Model(name, table, dsn, driver string) {
	ParamCheck("name", name)
	ParamCheck("dsn", dsn)
	if driver == "" {
		driver = "default"
	}

	conn, dbname := ConnectDSN(dsn)

	db, err := gorm.Open(conn, &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 使用单数表名
		},
	})
	HandleError(err)

	type Columns struct {
		ColumnName string `gorm:"column:COLUMN_NAME"`
		ColumnType string `gorm:"column:COLUMN_TYPE"`
		DataType   string `gorm:"column:DATA_TYPE"`
	}

	var columns = []*Columns{}
	err = db.Table("INFORMATION_SCHEMA.`COLUMNS`").
		Select([]string{"COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE"}).
		Where("TABLE_NAME=? AND TABLE_SCHEMA=?", table, dbname).
		Order("ORDINAL_POSITION ASC").
		Find(&columns).Error
	HandleError(err)

	modelName := name

	haveTimeImport := false
	for _, item := range columns {
		item.DataType = strings.ToLower(item.DataType)
		item.DataType = DbTypeFormat(item.DataType)

		if item.DataType == "datetime" || item.DataType == "timestamp" {
			haveTimeImport = true
		}
	}

	plugin := "model"
	currentDir := GetCurrentDir()
	pluginDir := filepath.Clean(fmt.Sprintf("%s/%s", currentDir, plugin))
	if exits, _ := PathExists(pluginDir); !exits {
		err = os.MkdirAll(pluginDir, 0755)
		if err != nil {
			HandleError("model 目录不存在")
		}
	}
	targetFilePath := filepath.Clean(fmt.Sprintf("%s/%s/%s.go", currentDir, plugin, modelName))

	if cHelper.IsExistsPath(targetFilePath) {
		contentByte, _ := os.ReadFile(targetFilePath)
		content := string(contentByte)

		var structDefined string
		structDefineds := FindStructFromContent(content)
		for _, defined := range structDefineds {
			if strings.Contains(defined, fmt.Sprintf("type %s struct", name)) {
				structDefined = defined
			}
		}

		if structDefined == "" {
			log.Panicf("model 文件未发现 %s struct: %s", name, targetFilePath)
			return
		}

		structure := fmt.Sprintf("type %s struct {\n\tcDB.Model[*%s]\n", modelName, modelName)
		for _, item := range columns {
			itemName := cHelper.ToCamel(item.ColumnName)
			if strings.ToLower(item.ColumnName) == "id" {
				itemName = "ID"
			}
			itemType := DbTypeToGoType(item.DataType, item.ColumnType)
			if item.ColumnName == "delete_time" {
				itemType = "soft_delete.DeletedAt"
				content = strings.ReplaceAll(content, "\"github.com/gin-gonic/gin\"", "\"github.com/gin-gonic/gin\"\n\t\"gorm.io/plugin/soft_delete\"")
			}

			var autoTimeStr string
			if item.ColumnName == "create_time" {
				autoTimeStr = ";autoCreateTime"
			}
			if item.ColumnName == "update_time" {
				autoTimeStr = ";autoUpdateTime"
			}

			structure = fmt.Sprintf("%s\t%s %s `gorm:\"column:%s%s\" json:\"%s\"`\n", structure, itemName, itemType, item.ColumnName, autoTimeStr, item.ColumnName)
		}
		structure = fmt.Sprintf("%s\n}", structure)

		content = strings.ReplaceAll(content, structDefined, structure)

		err = os.WriteFile(targetFilePath, []byte(content), 0755)
		HandleError(err)
	} else {
		importContent := "\n\nimport (\n\t\"gitee.com/csingo/cDB\"\n\t\"github.com/gin-gonic/gin\"\n)"
		if haveTimeImport {
			importContent = strings.ReplaceAll(importContent, ")", "\t\"time\"\n)")
		}

		content := fmt.Sprintf("package model%s\n\ntype %s struct {\n\tcDB.Model[*%s]\n", importContent, modelName, modelName)
		for _, item := range columns {
			itemName := cHelper.ToCamel(item.ColumnName)
			if strings.ToLower(item.ColumnName) == "id" {
				itemName = "ID"
			}
			itemType := DbTypeToGoType(item.DataType, item.ColumnType)
			if item.ColumnName == "delete_time" {
				itemType = "soft_delete.DeletedAt"
				content = strings.ReplaceAll(content, "\"github.com/gin-gonic/gin\"", "\"github.com/gin-gonic/gin\"\n\t\"gorm.io/plugin/soft_delete\"")
			}

			var autoTimeStr string
			if item.ColumnName == "create_time" {
				autoTimeStr = ";autoCreateTime"
			}
			if item.ColumnName == "update_time" {
				autoTimeStr = ";autoUpdateTime"
			}

			content = fmt.Sprintf("%s\t%s %s `gorm:\"column:%s%s\" json:\"%s\"`\n", content, itemName, itemType, item.ColumnName, autoTimeStr, item.ColumnName)
		}
		content = fmt.Sprintf("%s}\n\nfunc (i *%s) Connection(ctx *gin.Context) string {\n\treturn \"%s\"\n}", content, modelName, driver)
		if table != modelName {
			content = fmt.Sprintf("%s\n\nfunc (i *%s) TableName() string {\n\treturn \"%s\"\n}", content, modelName, table)
		}

		demoContent := i.contentFromDemo(plugin, "Model")
		demoAllContents := strings.Split(demoContent, "\n// ==========文件切割符，请勿编辑==========")
		methods := demoAllContents[1]

		methods = strings.ReplaceAll(methods, "HelloModel", name)
		content = content + "\n" + methods

		err = os.WriteFile(targetFilePath, []byte(content), 0755)
		HandleError(err)
	}

	projectDir := GetCurrentDir()
	Exec(fmt.Sprintf("go fmt %s", targetFilePath), projectDir)

	i.autoloadInject(name, "model")
	BeautifyImports()
}

func (i *ConsoleCommand) contentFromDemo(plugin, title string) (content string) {
	var err error
	var contentBytes []byte
	contentBytes, err = template.Asset(fmt.Sprintf("template/app/demo/%s/Hello%s.go.tpl", plugin, title))
	HandleError(err)

	content = string(contentBytes)
	return
}

func (i *ConsoleCommand) autoloadInject(name, plugin string) {
	pluginTitle := strings.Title(plugin)
	currentDir := GetCurrentDir()
	project, err := ProjectName()
	HandleError(err)
	// 判断autoload文件是否存在
	autoloadFilePath := filepath.Clean(fmt.Sprintf("%s/autoload/%s.go", currentDir, pluginTitle))
	if exits, err := PathExists(autoloadFilePath); !exits || err != nil {
		HandleError("autoload file 不存在")
	}

	content, err := os.ReadFile(autoloadFilePath)
	HandleError(err)
	autoloadContent := string(content)

	// 添加autoload文件import内容
	targetImportContent := fmt.Sprintf("\"%s/%s\"", project, plugin)
	if strings.Contains(autoloadContent, "import (") {
		if !strings.Contains(autoloadContent, targetImportContent) {
			autoloadContent = strings.Replace(autoloadContent, "import (", fmt.Sprintf("import (\n\t%s", targetImportContent), -1)
		}
	} else {
		autoloadContent = strings.Replace(autoloadContent, "package autoload", fmt.Sprintf("package autoload\n\nimport (\n\t\"gitee.com/csingo/cServer\"\n\t\"%s/%s\"\n)", project, plugin), -1)
	}

	// 添加autoload文件inject内容
	targetInjectContent := fmt.Sprintf("cServer.Inject(&%s.%s{})", plugin, name)
	demoInjectContent := fmt.Sprintf("func Load%s() {", pluginTitle)
	if !strings.Contains(autoloadContent, targetInjectContent) {
		autoloadContent = strings.Replace(autoloadContent, demoInjectContent, fmt.Sprintf("%s\n\t%s", demoInjectContent, targetInjectContent), -1)
	}

	err = os.WriteFile(autoloadFilePath, []byte(autoloadContent), 0755)
	HandleError(err)
}
